package com.mazaiting.socket.server;

import cn.hutool.core.io.IoUtil;
import com.mazaiting.common.core.domain.result.ResultCode;
import com.mazaiting.common.core.ex.exceptions.ConfigException;
import com.mazaiting.socket.enums.Operation;
import com.mazaiting.socket.server.entity.ServerConfig;
import com.mazaiting.socket.server.handler.IChannelHandler;
import com.mazaiting.socket.server.work.NioWorker;
import com.mazaiting.socket.util.NioUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Objects;
import java.util.concurrent.*;

/**
 * NIO服务器
 */
public class NioServer implements Runnable {
    /**
     * 日志工具
     */
    private final Logger logger = LoggerFactory.getLogger(getClass());
    /**
     * 是否运行, 用于结束线程
     */
    private boolean running = false;
    /**
     * 服务器Socket通道
     */
    private ServerSocketChannel serverSocketChannel;
    /**
     * 缓冲区
     */
    private final ByteBuffer readBuffer = ByteBuffer.allocate(2048);
    /**
     * 通道处理器
     */
    private IChannelHandler channelHandler;
    /**
     * 创建线程池
     * 核心池2个, 最大池10个, 保持存活时间30s, 线性阻塞队列
     * 默认的线程池工厂, 拒绝策略(全部拒绝)
     */
    public static final ExecutorService THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
            2, 10, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
            Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

    /**
     * 服务器配置
     */
    private final ServerConfig serverConfig;

    public NioServer(ServerConfig serverConfig) throws ConfigException {
        // 获取端口
        Integer port = serverConfig.getPort();
        // 校验端口是否为空
        if (Objects.isNull(port) || port <= 1024) {
            throw new ConfigException(ResultCode.SOCKET_SERVER_PORT_ERROR);
        }
        this.serverConfig = serverConfig;
    }

    /**
     * 启动服务
     */
    public void start() {
        // 判断是否在运行
        if (running) {
            logger.warn("服务器已经启动: " + serverConfig.getPort());
            return;
        }
        // 创建线程并启动
        Thread serverThread = new Thread(this);
        serverThread.start();
        // 设置运行中
        this.running = true;
    }

    /**
     * 停止
     */
    public void stop() {
        // 只有线程正在运行时关闭
        if (running) {
            // 设置停止运行
            this.running = false;
            IoUtil.close(serverSocketChannel);
        }
    }

    @Override
    public void run() {
        try {
            //打开ServerSocketChannel通道
            serverSocketChannel = ServerSocketChannel.open();
            //得到ServerSocket对象
            serverSocketChannel.socket().bind(new InetSocketAddress(serverConfig.getPort()));
            // 打开选择器
            Selector selector = Selector.open();
            // 注册
            NioUtil.registerChannel(selector, serverSocketChannel, Operation.ACCEPT);
            logger.info("启动成功: " + serverConfig);
            // 使用状态位标识线程停止时机
            while (running) {
                // 等待
                selector.select();
                // 读取键
                Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();
                SelectionKey key;
                // 遍历键, 如果存在下一个则读取
                while (selectedKeys.hasNext()) {
                    // 获取并移除下一个键
                    key = selectedKeys.next();
                    selectedKeys.remove();
                    // 判断键是否可用
                    if (!key.isValid()) {
                        continue;
                    }
                    if (key.isAcceptable()) {
                        // 判断是否可访问
                        accept(key);
                    } else if (key.isReadable()) {
                        // 判断是否可读
                        read(key);
                    } else if (key.isWritable()) {
                        // 判断是否可写
                        write(key);
                    } else {
                        logger.warn("端口" + serverConfig.getPort() + ", 无效键: " + key);
                    }
                }
            }
        } catch (Exception e) {
            logger.error("NioServer-" + serverConfig.getPort() + "运行异常: " + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 可访问键
     *
     * @param key 键
     */
    private void accept(SelectionKey key) {
        try {
            // 获取通道
            ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
            // 访问
            SocketChannel socketChannel = serverSocketChannel.accept();
            // 注册
            NioUtil.registerChannel(key.selector(), socketChannel, Operation.READ);
        } catch (Exception e) {
            logger.error("NioServer-" + serverConfig.getPort() + "接受访问异常: " + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 读取通道内容
     *
     * @param key 键
     */
    private void read(SelectionKey key) {
        // 获取通道
        SocketChannel socketChannel = (SocketChannel) key.channel();
        try {
            // 读取内容到缓冲区
            int readNum = socketChannel.read(readBuffer);
            // 判断是否读取到内容
            if (readNum == -1) {
                IoUtil.close(socketChannel);
                key.cancel();
                return;
            }
            // 翻转缓冲区
            readBuffer.flip();
            // 读取缓冲区内容到字节数组
            byte[] buffers = new byte[readBuffer.limit()];
            readBuffer.get(buffers);
            // 清除缓冲
            readBuffer.clear();
            // 请求内容
            String requestStr = new String(buffers, serverConfig.getEncoding());
            // 使用线程池处理
            THREAD_POOL_EXECUTOR.execute(new NioWorker(key, serverConfig, requestStr, channelHandler));
        } catch (Exception e) {
            logger.error("NioServer-" + serverConfig.getPort() + "读取异常: " + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 写内容
     *
     * @param key 键
     */
    private void write(SelectionKey key) {
        // 获取通道
        try (SocketChannel socketChannel = (SocketChannel) key.channel()) {
            // 获取附件
            ByteBuffer buffer = (ByteBuffer) key.attachment();
            // 判断是否有内容给
            if (Objects.isNull(buffer) || !buffer.hasRemaining()) {
                return;
            }
            // 写入内容
            socketChannel.write(buffer);
            // 判断是否内容
            if (!buffer.hasRemaining()) {
                key.interestOps(SelectionKey.OP_READ);
                buffer.clear();
            }
        } catch (Exception e) {
            logger.error("NioServer-" + serverConfig.getPort() + "写入异常: " + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 设置通道处理器
     *
     * @param handler 通道处理器
     */
    public void setChannelHandler(IChannelHandler handler) {
        this.channelHandler = handler;
    }
}